/*
 *  pilua_field.c
 *
 * Wireshark's interface to the Lua Programming Language
 *
 * (c) 2006, Luis E. Garcia Ontanon <luis@ontanon.org>
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include <stdint.h>
#include <stdbool.h>
#include "tvbuff.h"
#include "packet.h"
#include "ftypes.h"
#include "proto.h"
#include "value_string.h"
#include "true_false_strings.h"
#include "unit_strings.h"

/* PILUA_MODULE Field Obtaining Dissection Data */

#include "pilua.h"

/* any call to checkFieldInfo() will now error on null or expired, so no need to check again */
PILUA_CLASS_DEFINE(FieldInfo, FAIL_ON_NULL_OR_EXPIRED("FieldInfo"));
/*
   An extracted Field from dissected packet data. A `FieldInfo` object can only be used within
   the callback functions of dissectors, post-dissectors, heuristic-dissectors, and taps.

   A `FieldInfo` can be called on either existing Wireshark fields by using either `Field.new()`
   or `Field()` before-hand, or it can be called on new fields created by Lua from a `ProtoField`.
 */

//static GPtrArray* outstanding_FieldInfo;

//FieldInfo* push_FieldInfo(lua_State* L, field_info* f)
//{
//    FieldInfo fi = (FieldInfo)malloc(sizeof(struct _pilua_field_info));
////    fi->pi_fi = f;
//    fi->expired = false;
////    g_ptr_array_add(outstanding_FieldInfo, fi);
//    return pushFieldInfo(L, fi);
//}

//CLEAR_OUTSTANDING(FieldInfo, expired, true)

/* PILUA_ATTRIBUTE FieldInfo_len RO The length of this field. */
PILUA_METAMETHOD FieldInfo__len(lua_State* L)
{
    /*
       Obtain the Length of the field
       */
    FieldInfo fi = checkFieldInfo(L, 1);

//    lua_pushinteger(L, fi->pi_fi->length);
    return 1;
}

/* PILUA_ATTRIBUTE FieldInfo_offset RO The offset of this field. */
PILUA_METAMETHOD FieldInfo__unm(lua_State* L)
{
    /*
       Obtain the Offset of the field
       */
    FieldInfo fi = checkFieldInfo(L, 1);

//    lua_pushinteger(L, fi->pi_fi->start);
    return 1;
}

/* PILUA_ATTRIBUTE FieldInfo_value RO The value of this field. */
PILUA_METAMETHOD FieldInfo__call(lua_State* L)
{
    /*
       Obtain the Value of the field.

       Previous to 1.11.4, this function retrieved the value for most field types,
       but for `ftypes.UINT_BYTES` it retrieved the `ByteArray` of the field's entire `TvbRange`.
       In other words, it returned a `ByteArray` that included the leading length byte(s),
       instead of just the *value* bytes. That was a bug, and has been changed in 1.11.4.
       Furthermore, it retrieved an `ftypes.GUID` as a `ByteArray`, which is also incorrect.

       If you wish to still get a `ByteArray` of the `TvbRange`, use `fieldinfo.range`
       to get the `TvbRange`, and then use `tvbrange:bytes()` to convert it to a `ByteArray`.
       */
    FieldInfo fi = checkFieldInfo(L, 1);

//    switch(fi->pi_fi->hfinfo->type) {
//        case FT_BOOLEAN:
//                lua_pushboolean(L, (int)fvalue_get_uinteger64(fi->pi_fi->value));
//                return 1;
//        case FT_CHAR:
//        case FT_UINT8:
//        case FT_UINT16:
//        case FT_UINT24:
//        case FT_UINT32:
//        case FT_FRAMENUM:
//                lua_pushinteger(L, (lua_Integer)(fvalue_get_uinteger(fi->pi_fi->value)));
//                return 1;
//        case FT_INT8:
//        case FT_INT16:
//        case FT_INT24:
//        case FT_INT32:
//                lua_pushinteger(L, (lua_Integer)(fvalue_get_sinteger(fi->pi_fi->value)));
//                return 1;
//        case FT_FLOAT:
//        case FT_DOUBLE:
//                lua_pushnumber(L, (lua_Number)(fvalue_get_floating(fi->pi_fi->value)));
//                return 1;
//        case FT_INT64: {
//                pushInt64(L, (Int64)(fvalue_get_sinteger64(fi->pi_fi->value)));
//                return 1;
//            }
//        case FT_UINT64: {
//                pushUInt64(L, fvalue_get_uinteger64(fi->pi_fi->value));
//                return 1;
//            }
//        case FT_ETHER: {
//                Address eth = (Address)g_malloc(sizeof(address));
//                alloc_address_tvb(NULL,eth,AT_ETHER,fi->pi_fi->length,fi->pi_fi->ds_tvb,fi->pi_fi->start);
//                pushAddress(L,eth);
//                return 1;
//            }
//        case FT_IPv4:{
//                Address ipv4 = (Address)g_malloc(sizeof(address));
//                alloc_address_tvb(NULL,ipv4,AT_IPv4,fi->pi_fi->length,fi->pi_fi->ds_tvb,fi->pi_fi->start);
//                pushAddress(L,ipv4);
//                return 1;
//            }
//        case FT_IPv6: {
//                Address ipv6 = (Address)g_malloc(sizeof(address));
//                alloc_address_tvb(NULL,ipv6,AT_IPv6,fi->pi_fi->length,fi->pi_fi->ds_tvb,fi->pi_fi->start);
//                pushAddress(L,ipv6);
//                return 1;
//            }
//        case FT_FCWWN: {
//                Address fcwwn = (Address)g_malloc(sizeof(address));
//                alloc_address_tvb(NULL,fcwwn,AT_FCWWN,fi->pi_fi->length,fi->pi_fi->ds_tvb,fi->pi_fi->start);
//                pushAddress(L,fcwwn);
//                return 1;
//            }
//        case FT_IPXNET:{
//                Address ipx = (Address)g_malloc(sizeof(address));
//                alloc_address_tvb(NULL,ipx,AT_IPX,fi->pi_fi->length,fi->pi_fi->ds_tvb,fi->pi_fi->start);
//                pushAddress(L,ipx);
//                return 1;
//            }
//        case FT_ABSOLUTE_TIME:
//        case FT_RELATIVE_TIME: {
//                NSTime nstime = (NSTime)g_malloc(sizeof(nstime_t));
//                *nstime = *fvalue_get_time(fi->pi_fi->value);
//                pushNSTime(L,nstime);
//                return 1;
//            }
//        case FT_STRING:
//        case FT_STRINGZ:
//        case FT_STRINGZPAD: {
//                char* repr = fvalue_to_string_repr(NULL, fi->pi_fi->value, FTREPR_DISPLAY, BASE_NONE);
//                if (repr)
//                {
//                    lua_pushstring(L, repr);
//                    wmem_free(NULL, repr);
//                }
//                else
//                {
//                    luaL_error(L,"field cannot be represented as string because it may contain invalid characters");
//                }
//                return 1;
//            }
//        case FT_NONE:
//                if (fi->pi_fi->length > 0 && fi->pi_fi->rep) {
//                    /* it has a length, but calling fvalue_get() on an FT_NONE asserts,
//                       so get the label instead (it's a FT_NONE, so a label is what it basically is) */
//                    lua_pushstring(L, fi->pi_fi->rep->representation);
//                    return 1;
//                }
//                return 0;
//        case FT_BYTES:
//        case FT_UINT_BYTES:
//        case FT_REL_OID:
//        case FT_SYSTEM_ID:
//        case FT_OID:
//        {
//            ByteArray ba = g_byte_array_new();
//            g_byte_array_append(ba, fvalue_get_bytes_data(fi->pi_fi->value),
//                                (unsigned)fvalue_length2(fi->pi_fi->value));
//            pushByteArray(L,ba);
//            return 1;
//        }
//        case FT_PROTOCOL:
//        {
//            ByteArray ba = g_byte_array_new();
//            tvbuff_t* tvb = fvalue_get_protocol(fi->pi_fi->value);
//            uint8_t* raw;
//            if (tvb != NULL) {
//                raw = (uint8_t *)tvb_memdup(NULL, tvb, 0, tvb_captured_length(tvb));
//                g_byte_array_append(ba, raw, tvb_captured_length(tvb));
//                wmem_free(NULL, raw);
//            }

//            pushByteArray(L,ba);
//            return 1;
//        }

//        case FT_GUID:
//        default:
//            luaL_error(L,"FT_ not yet supported");
//            return 1;
//    }
}

/* PILUA_ATTRIBUTE FieldInfo_label RO The string representing this field. */
PILUA_METAMETHOD FieldInfo__tostring(lua_State* L)
{
    /* The string representation of the field. */
    FieldInfo fi = checkFieldInfo(L, 1);

    char* repr = NULL;

//    if (fi->pi_fi->hfinfo->type == FT_PROTOCOL) {
//        repr = fvalue_to_string_repr(NULL, fi->pi_fi->value, FTREPR_DFILTER, BASE_NONE);
//    }
//    else {
//        repr = fvalue_to_string_repr(NULL, fi->pi_fi->value, FTREPR_DISPLAY, fi->pi_fi->hfinfo->display);
//    }

//    if (repr) {
//        lua_pushstring(L, repr);
//        /* fvalue_to_string_repr() wmem_alloc's the string's buffer */
//        wmem_free(NULL, repr);
//    }
//    else {
//        lua_pushstring(L, "(unknown)");
//    }

    return 1;
}

/* PILUA_ATTRIBUTE FieldInfo_display RO The string display of this field as seen in GUI. */
static int FieldInfo_get_display(lua_State* L)
{
    /* The display string of this field as seen in GUI. */
    FieldInfo fi = checkFieldInfo(L, 1);
//    char          label_str[ITEM_LABEL_LENGTH+1];
//    char         *label_ptr;
//    char         *value_ptr;

//    if (!fi->pi_fi->rep) {
//        label_ptr = label_str;
//        proto_item_fill_label(fi->pi_fi, label_str);
//    } else
//        label_ptr = fi->pi_fi->rep->representation;

//    if (!label_ptr) return 0;

//    value_ptr = strstr(label_ptr, ": ");
//    if (!value_ptr) {
//        /* just use whatever's there */
//        lua_pushstring(L, label_ptr);
//    } else {
//        value_ptr += 2;  /* get past the ': ' */
//        lua_pushstring(L, value_ptr);
//    }

    return 1;
}

/* PILUA_ATTRIBUTE FieldInfo_type RO The internal field type, a number which
   matches one of the `ftype` values.
 */
static int FieldInfo_get_type(lua_State* L)
{
    FieldInfo fi = checkFieldInfo(L, 1);

//    if (fi->pi_fi->hfinfo) {
//        lua_pushinteger(L, fi->pi_fi->hfinfo->type);
//    }
//    else {
//        lua_pushnil(L);
//    }

    return 1;
}

/* PILUA_ATTRIBUTE FieldInfo_source RO The source `Tvb` object the `FieldInfo` is derived
    from, or nil if there is none.
 */
static int FieldInfo_get_source(lua_State* L)
{
    FieldInfo fi = checkFieldInfo(L, 1);

//    if (fi->pi_fi->ds_tvb) {
//        push_Tvb(L, fi->pi_fi->ds_tvb);
//    }
//    else {
//        lua_pushnil(L);
//    }

    return 1;
}

/* PILUA_ATTRIBUTE FieldInfo_range RO The `TvbRange` covering the bytes of this field in a Tvb or nil if there is none. */
static int FieldInfo_get_range(lua_State* L)
{
    FieldInfo fi = checkFieldInfo(L, 1);

//    if (!fi->pi_fi->ds_tvb) {
//        lua_pushnil(L);
//        return 1;
//    }

//    if (push_TvbRange (L, fi->pi_fi->ds_tvb, fi->pi_fi->start, fi->pi_fi->length)) {
//        return 1;
//    }

    return 0;
}

/* PILUA_ATTRIBUTE FieldInfo_generated RO Whether this field was marked as generated (boolean). */
static int FieldInfo_get_generated(lua_State* L)
{
    /* Whether this field was marked as generated. */
    FieldInfo fi = checkFieldInfo(L, 1);

//    lua_pushboolean(L, FI_GET_FLAG(fi->pi_fi, FI_GENERATED));
    return 1;
}

/* PILUA_ATTRIBUTE FieldInfo_hidden RO Whether this field was marked as hidden (boolean). */
static int FieldInfo_get_hidden(lua_State* L)
{
    FieldInfo fi = checkFieldInfo(L, 1);

//    lua_pushboolean(L, FI_GET_FLAG(fi->pi_fi, FI_HIDDEN));
    return 1;
}

/* PILUA_ATTRIBUTE FieldInfo_is_url RO Whether this field was marked as being a URL (boolean). */
static int FieldInfo_get_is_url(lua_State* L)
{
    FieldInfo fi = checkFieldInfo(L, 1);

//    lua_pushboolean(L, FI_GET_FLAG(fi->pi_fi, FI_URL));
    return 1;
}

/* PILUA_ATTRIBUTE FieldInfo_little_endian RO Whether this field is little-endian encoded (boolean). */
static int FieldInfo_get_little_endian(lua_State* L)
{
    FieldInfo fi = checkFieldInfo(L, 1);

//    lua_pushboolean(L, FI_GET_FLAG(fi->pi_fi, FI_LITTLE_ENDIAN));
    return 1;
}

/* PILUA_ATTRIBUTE FieldInfo_big_endian RO Whether this field is big-endian encoded (boolean). */
static int FieldInfo_get_big_endian(lua_State* L)
{
    FieldInfo fi = checkFieldInfo(L, 1);

//    lua_pushboolean(L, FI_GET_FLAG(fi->pi_fi, FI_BIG_ENDIAN));
    return 1;
}

/* PILUA_ATTRIBUTE FieldInfo_name RO The filter name of this field. */
static int FieldInfo_get_name(lua_State* L)
{
    /* The filter name of this field. */
    FieldInfo fi = checkFieldInfo(L, 1);

//    lua_pushstring(L, fi->pi_fi->hfinfo->abbrev);
    return 1;
}

PILUA_METAMETHOD FieldInfo__eq(lua_State* L)
{
    /* Checks whether lhs is within rhs. */
    FieldInfo l = checkFieldInfo(L, 1);
    FieldInfo r = checkFieldInfo(L, 2);

    /* it is not an error if their ds_tvb are different... they're just not equal */
//    if (l->pi_fi->ds_tvb == r->pi_fi->ds_tvb &&
//        l->pi_fi->start == r->pi_fi->start &&
//        r->pi_fi->length == l->pi_fi->length) {
//        lua_pushboolean(L, 1);
//    } else {
//        lua_pushboolean(L, 0);
//    }
    return 1;
}

PILUA_METAMETHOD FieldInfo__le(lua_State* L)
{
    /* Checks whether the end byte of lhs is before the end of rhs. */
    FieldInfo l = checkFieldInfo(L,1);
    FieldInfo r = checkFieldInfo(L,2);

//    if (l->pi_fi->ds_tvb != r->pi_fi->ds_tvb)
//        PILUA_ERROR(FieldInfo__le, "Data source must be the same for both fields");

//    if (l->pi_fi->start + l->pi_fi->length <= r->pi_fi->start + r->pi_fi->length) {
//        lua_pushboolean(L, 1);
//    } else {
//        lua_pushboolean(L, 0);
//    }
    return 1;
}

PILUA_METAMETHOD FieldInfo__lt(lua_State* L)
{
    /* Checks whether the end byte of lhs is before the beginning of rhs. */
    FieldInfo l = checkFieldInfo(L, 1);
    FieldInfo r = checkFieldInfo(L, 2);

//    if (l->pi_fi->ds_tvb != r->pi_fi->ds_tvb) {
//        PILUA_ERROR(FieldInfo__lt,"Data source must be the same for both fields");
//        return 0;
//    }

//    if (l->pi_fi->start + l->pi_fi->length <= r->pi_fi->start) {
//        lua_pushboolean(L, 1);
//    } else {
//        lua_pushboolean(L, 0);
//    }
    return 1;
}

/* Gets registered as metamethod automatically by PILUA_REGISTER_META */
static int FieldInfo__gc(lua_State* L)
{
    FieldInfo fi = toFieldInfo(L, 1);

    if (!fi) return 0;

    if (!fi->expired)
        fi->expired = true;
    else
        /* do NOT free fi->pi_fi */
        free(fi);

    return 0;
}

/* This table is ultimately registered as a sub-table of the class' metatable,
 * and if __index/__newindex is invoked then it calls the appropriate function
 * from this table for getting/setting the members.
 */
PILUA_ATTRIBUTES FieldInfo_attributes[] = {
    PILUA_ATTRIBUTE_ROREG(FieldInfo, range),
    PILUA_ATTRIBUTE_ROREG(FieldInfo, generated),
    PILUA_ATTRIBUTE_ROREG(FieldInfo, hidden),
    PILUA_ATTRIBUTE_ROREG(FieldInfo, is_url),
    PILUA_ATTRIBUTE_ROREG(FieldInfo, little_endian),
    PILUA_ATTRIBUTE_ROREG(FieldInfo, big_endian),
    PILUA_ATTRIBUTE_ROREG(FieldInfo, name),
    PILUA_ATTRIBUTE_ROREG(FieldInfo, display),
    PILUA_ATTRIBUTE_ROREG(FieldInfo, type),
    PILUA_ATTRIBUTE_ROREG(FieldInfo, source),
    { "label", FieldInfo__tostring, NULL },
    { "value", FieldInfo__call, NULL },
    { "tvb", FieldInfo_get_range, NULL },
    { "len", FieldInfo__len, NULL },
    { "offset", FieldInfo__unm, NULL },
    { NULL, NULL, NULL }
};

PILUA_META FieldInfo_meta[] = {
    PILUA_CLASS_MTREG(FieldInfo, tostring),
    PILUA_CLASS_MTREG(FieldInfo, call),
    PILUA_CLASS_MTREG(FieldInfo, len),
    PILUA_CLASS_MTREG(FieldInfo, unm),
    PILUA_CLASS_MTREG(FieldInfo, eq),
    PILUA_CLASS_MTREG(FieldInfo, le),
    PILUA_CLASS_MTREG(FieldInfo, lt),
    { NULL, NULL }
};

int FieldInfo_register(lua_State* L)
{
    PILUA_REGISTER_META_WITH_ATTRS(FieldInfo);
    return 0;
}


PILUA_FUNCTION pilua_all_field_infos(lua_State* L)
{
    /*
    Obtain all fields from the current tree.  Note this only gets whatever fields the underlying
    dissectors have filled in for this packet at this time - there may be fields applicable to
    the packet that simply aren't being filled in because at this time they're not needed for anything.
    This function only gets what the C-side code has currently populated, not the full list.
    */
//    GPtrArray* found;
    int items_found = 0;
    unsigned i;

//    if (! lua_tree || ! lua_tree->tree ) {
//        PILUA_ERROR(pilua_all_field_infos,"Cannot be called outside a listener or dissector");
//        return 0;
//    }

//    found = proto_all_finfos(lua_tree->tree);

//    if (found) {
//        for (i = 0; i < found->len; i++) {
//            push_FieldInfo(L, (field_info *)g_ptr_array_index(found, i));
//            items_found++;
//        }

//        g_ptr_array_free(found, true);
//    }

    return items_found;
}

PILUA_CLASS_DEFINE(Field, FAIL_ON_NULL("Field"));
/*
   A Field extractor to obtain field values. A `Field` object can only be created *outside* of
   the callback functions of dissectors, post-dissectors, heuristic-dissectors, and taps.

   Once created, it is used *inside* the callback functions, to generate a `FieldInfo` object.
 */

/* Array of Field (struct _pilua_header_field_info*) pointers.*/
//static GPtrArray* wanted_fields;
//static dfilter_t* pilua_dfilter;

/* We use a fake dfilter for Lua field extractors, so that
 * epan_dissect_run() will populate the fields.  This won't happen
 * if the passed-in edt->tree is NULL, which it will be if the
 * proto_tree isn't created by epan_dissect_init().  But that's by
 * design - if shark doesn't pass in a proto_tree, it's probably for
 * a good reason and we shouldn't override that. (right?)
 */
//void pilua_prime_dfilter(epan_dissect_t *edt)
//{
//    if (pilua_dfilter && edt && edt->tree) {
//        dfilter_prime_proto_tree(pilua_dfilter, edt->tree);
//    }
//}

/* Check if we have any registered field extractors. */
//bool pilua_has_field_extractors(void)
//{
//    return (pilua_dfilter && dfilter_has_interesting_fields(pilua_dfilter));
//}

/*
 * field extractor registration is tricky, In order to allow
 * the user to define them in the body of the script we will
 * populate the Field value with a pointer of the abbrev of it
 * to later replace it with the hfi.
 *
 * This will be added to the wanted_fields array that will
 * exists only while they can be defined, and be cleared right
 * after the fields are primed.
 */

static bool fake_tap;
//void lua_prime_all_fields(proto_tree* tree)
//{
//    GString* fake_tap_filter = g_string_new("frame");
//    unsigned i;
//    df_error_t *df_err;

//    for(i=0; i < wanted_fields->len; i++) {
//        Field f = (Field)g_ptr_array_index(wanted_fields, i);

//        f->hfi = proto_registrar_get_byname(f->name);
//        if (!f->hfi) {
//            report_failure("Could not find field `%s'", f->name);
//            continue;
//        }

//        g_string_append_printf(fake_tap_filter, " || %s", f->hfi->abbrev);
//        fake_tap = true;
//    }

//    g_ptr_array_free(wanted_fields, true);
//    wanted_fields = NULL;

//    if (fake_tap && fake_tap_filter->len > strlen("frame")) {
//        /* a boring tap :-) */
//        GString* error = register_tap_listener("frame",
//                &fake_tap,
//                fake_tap_filter->str,
//                0, /* XXX - do we need the protocol tree or columns? */
//                NULL, NULL, NULL, NULL);

//        if (error) {
//            report_failure("while registering lua_fake_tap:\n%s", error->str);
//            g_strinfree(error, TRUE);
//        } else if (!dfilter_compile(fake_tap_filter->str, &pilua_dfilter, &df_err)) {
//            report_failure("while compiling dfilter \"%s\" for pilua: %s", fake_tap_filter->str, df_err->msg);
//            df_error_free(&df_err);
//        }
//    }
//    g_strinfree(fake_tap_filter, TRUE);
//}

PILUA_CONSTRUCTOR Field_new(lua_State *L)
{
    /*
       Create a Field extractor.
       */
#define PILUA_ARG_Field_new_FIELDNAME 1 /* The filter name of the field (e.g. ip.addr) */
    const char* name = luaL_checkstring(L, PILUA_ARG_Field_new_FIELDNAME);
    Field f;

//    if (!proto_registrar_get_byname(name) && !pilua_is_field_available(L, name)) {
//        PILUA_ARG_ERROR(Field_new, FIELDNAME, "a field with this name must exist");
//        return 0;
//    }

//    if (!wanted_fields) {
//        PILUA_ERROR(Field_new, "A Field extractor must be defined before Taps or Dissectors get called");
//        return 0;
//    }

    f = (Field)malloc(sizeof(struct _pilua_header_field_info));
    f->name = strdup(name);

//    g_ptr_array_add(wanted_fields, f);

    pushField(L, f);
    PILUA_RETURN(1); /* The field extractor */
}

PILUA_CONSTRUCTOR Field_list(lua_State *L)
{
    /* Gets a Lua array table of all registered field filter names.

       NOTE: This is an expensive operation, and should only be used for troubleshooting.
     */
    void *cookie, *cookie2;
    int i = -1;
    int count = 0;
//    header_field_info *hfinfo = NULL;

//    lua_newtable(L);

//    for (i = proto_get_first_protocol(&cookie); i != -1;
//         i = proto_get_next_protocol(&cookie)) {

//        for (hfinfo = proto_get_first_protocol_field(i, &cookie2); hfinfo != NULL;
//             hfinfo = proto_get_next_protocol_field(i, &cookie2)) {

//            if (hfinfo->same_name_prev_id != -1) /* ignore duplicate names */
//                continue;

//            count++;
//            lua_pushstring(L, hfinfo->abbrev);
//            lua_rapieti(L, -2, count);
//        }
//    }

    PILUA_RETURN(1); /* The array table of field filter names */
}

/* the following is used in Field_get_xxx functions later. If called early
 * (wanted_fields is not NULL), it will try to retrieve information directly.
 * Otherwise it uses a cached field that was loaded in lua_prime_all_fields. */
#define GET_HFINFO_MEMBER(luafunc, member)                          \
    if (wanted_fields) {                                            \
        hfinfo = proto_registrar_get_byname(f->name);               \
        if (!hfinfo) {                                              \
            /* could be a Lua-created field */                      \
            ProtoField pf = pilua_is_field_available(L, f->name);   \
            if (pf) {                                               \
                luafunc(L, pf->member);                             \
                return 1;                                           \
            }                                                       \
        }                                                           \
    } else {                                                        \
        hfinfo = f->hfi;                                            \
    }                                                               \
                                                                    \
    if (hfinfo) {                                                   \
        luafunc(L, hfinfo->member);                                  \
    } else                                                          \
        lua_pushnil(L)


/* PILUA_ATTRIBUTE Field_name RO The filter name of this field, or nil. */
static int Field_get_name(lua_State* L)
{
    Field f = checkField(L, 1);
//    header_field_info* hfinfo = NULL;

//    GET_HFINFO_MEMBER(lua_pushstring, abbrev);

    return 1;
}

/* PILUA_ATTRIBUTE Field_display RO The full display name of this field, or nil. */
static int Field_get_display(lua_State* L)
{
    Field f = checkField(L, 1);
//    header_field_info* hfinfo = NULL;

//    GET_HFINFO_MEMBER(lua_pushstring, name);

    return 1;
}

/* PILUA_ATTRIBUTE Field_type RO The `ftype` of this field, or nil. */
static int Field_get_type(lua_State* L)
{
    Field f = checkField(L, 1);
//    header_field_info* hfinfo = NULL;

//    GET_HFINFO_MEMBER(lua_pushinteger, type);

    return 1;
}

PILUA_METAMETHOD Field__call(lua_State* L)
{
    /* Obtain all values (see `FieldInfo`) for this field. */
    Field f = checkField(L, 1);
//    header_field_info* in = f->hfi;
    int items_found = 0;

//    if (! in) {
//        luaL_error(L, "invalid field");
//        return 0;
//    }

//    if (! lua_pinfo) {
//        PILUA_ERROR(Field__call, "Fields cannot be used outside dissectors or taps");
//        return 0;
//    }

//    while (in) {
//        GPtrArray* found = proto_get_finfo_ptr_array(lua_tree->tree, in->id);
//        unsigned i;
//        if (found) {
//            for (i=0; i<found->len; i++) {
//                push_FieldInfo(L, (field_info *) g_ptr_array_index(found,i));
//                items_found++;
//            }
//        }
//        in = (in->same_name_prev_id != -1) ? proto_registrar_get_nth(in->same_name_prev_id) : NULL;
//    }

    PILUA_RETURN(items_found); /* All the values of this field */
}

PILUA_METAMETHOD Field__tostring(lua_State* L)
{
    /* Obtain a string with the field filter name. */
    Field f = checkField(L, 1);

//    if (f->hfi) {
//        /* If a field was found, return the actual field info. */
//        lua_pushstring(L, f->hfi->abbrev);
//    } else {
//        lua_pushstring(L, f->name);
//    }

    return 1;
}

static int Field__gc(lua_State* L)
{
    Field f = toField(L, 1);
    if (!f) return 0;

    // If out of scope before lua_prime_all_fields is even called, be sure to
    // remove the pointer to avoid a use-after-free.
//    if (wanted_fields) {
//        g_ptr_array_remove_fast(wanted_fields, f);
//    }

    free(f->name);
    free(f);
    return 0;
}

PILUA_ATTRIBUTES Field_attributes[] = {
    PILUA_ATTRIBUTE_ROREG(Field, name),
    PILUA_ATTRIBUTE_ROREG(Field, display),
    PILUA_ATTRIBUTE_ROREG(Field, type),
    { NULL, NULL, NULL }
};

PILUA_METHODS Field_methods[] = {
    PILUA_CLASS_FNREG(Field, new),
    PILUA_CLASS_FNREG(Field, list),
    { NULL, NULL }
};

PILUA_META Field_meta[] = {
    PILUA_CLASS_MTREG(Field, tostring),
    PILUA_CLASS_MTREG(Field, call),
    { NULL, NULL }
};

int Field_register(lua_State* L)
{
//    if (wanted_fields != NULL) {
//        g_ptr_array_unref(wanted_fields);
//    }
//    wanted_fields = g_ptr_array_new();

    PILUA_REGISTER_CLASS_WITH_ATTRS(Field);
//    if (outstanding_FieldInfo != NULL) {
//        g_ptr_array_unref(outstanding_FieldInfo);
//    }
//    outstanding_FieldInfo = g_ptr_array_new();

    return 0;
}

int pilua_deregister_fields(lua_State* L)
{
//    if (pilua_dfilter) {
//        dfilter_free(pilua_dfilter);
//        pilua_dfilter = NULL;
//    }

//    if (fake_tap) {
//        remove_tap_listener(&fake_tap);
//        fake_tap = false;
//    }

    return 0;
}
